package com.demo.common.swagger.plugin;

import com.demo.common.support.model.DataResponse;
import com.demo.common.support.model.EmptyDataResponse;
import com.demo.common.support.model.MultipleDataResponse;
import com.demo.common.support.model.SimpleDataResponse;
import com.demo.common.support.page.PageResponse;
import com.demo.common.swagger.annotation.ApiDataResponse;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Function;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import springfox.documentation.schema.plugins.SchemaPluginsManager;
import springfox.documentation.schema.property.ModelSpecificationFactory;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ViewProviderPlugin;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ResponseContext;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

import java.util.*;

/**
 * 扩展支持{@link ApiDataResponse} 注解
 * 解决返回值DataResponse<T> 文档中没有返回值实体的信息
 *
 * @author molong
 * @since 2024/6/11
 */
@Order(SwaggerDataResponseMessageReader.PLUGIN_ORDER)
@SuppressWarnings("all")
public class SwaggerDataResponseMessageReader implements OperationBuilderPlugin {

    private final SchemaPluginsManager pluginsManager;
    private final TypeResolver typeResolver;
    private final ModelSpecificationFactory modelSpecifications;
    private final DocumentationPluginsManager documentationPlugins;

    public SwaggerDataResponseMessageReader(SchemaPluginsManager pluginsManager,TypeResolver typeResolver,
                                            ModelSpecificationFactory modelSpecifications,DocumentationPluginsManager documentationPlugins) {
        this.pluginsManager = pluginsManager;
        this.typeResolver = typeResolver;
        this.modelSpecifications = modelSpecifications;
        this.documentationPlugins = documentationPlugins;
    }

    final static int PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 1001;

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }

    @Override
    public void apply(OperationContext context) {
        Optional<ApiDataResponse> apiOperationOptional = context.findAnnotation(ApiDataResponse.class);
        ResolvedType resolvedType = context.getReturnType();
        if (apiOperationOptional.isPresent() && fromDataRespose(resolvedType)) {
            ApiDataResponse response = apiOperationOptional.get();
            ResolvedType responseType = alternativeTypeResolver(resolvedType, response.responseType());
            ResolvedType responseResolvedType = resolvedType(response, responseType);
            ViewProviderPlugin viewProvider =pluginsManager.viewProvider(context.getDocumentationContext().getDocumentationType());
            ModelContext modelContext = ModelContext.returnValue("", context.getGroupName(), responseResolvedType,
                    viewProvider.viewFor(context), context.getDocumentationType(),
                    context.getAlternateTypeProvider(), context.getGenericsNamingStrategy(), context.getIgnorableParameterTypes());

            //处理swagger的接口文档的返回值
            ResponseContext responseContext = new ResponseContext(context.getDocumentationContext(), context);
            responseContext.responseBuilder()
                    .description(response.message())
                    .headers(new ArrayList<>())
                    .code(String.valueOf(response.code()));
            Set<MediaType> produces = new HashSet<>(context.produces());
            if (produces.isEmpty()) {
                produces.add(MediaType.ALL);
            }
            produces.forEach(mediaType ->
                                     responseContext.responseBuilder()
                                             .representation(mediaType)
                                             .apply(r -> r.model(m -> m.copyOf(modelSpecifications.create(modelContext, responseResolvedType)))));
            context.operationBuilder()
                    .responses(Collections.singleton(documentationPlugins.response(responseContext)));
        }
    }


    private ResolvedType resolvedType(ApiDataResponse apiResponse, ResolvedType responseType) {
        return resolvedTypeFromResponse(typeResolver, responseType).apply(apiResponse);
    }

    private static Optional<ResolvedType> getGenericType(TypeResolver typeResolver, ResolvedType responseType, ApiDataResponse apiDataResponse) {
        if (responseType.getTypeParameters().isEmpty()) {
            return Optional.of(typeResolver.resolve(apiDataResponse.responseType()));
        }
        return Optional.ofNullable(responseType.getTypeParameters().get(0));
    }


    private static Function<ApiDataResponse, ResolvedType> resolvedTypeFromResponse(final TypeResolver typeResolver, ResolvedType responseType) {
        return annotation -> {
            ResolvedType resolvedType = getGenericType(typeResolver, responseType, annotation).orElse(null);
            if (annotation.pageable()) {
                return typeResolver.resolve(PageResponse.class, resolvedType);
            }
            if (annotation.collectable()) {
                return typeResolver.resolve(MultipleDataResponse.class, resolvedType);
            }
            if (isNotVoid(resolvedType.getErasedType())) {
                return typeResolver.resolve(SimpleDataResponse.class, resolvedType);
            }
            return typeResolver.resolve(EmptyDataResponse.class);
        };
    }

    private ResolvedType alternativeTypeResolver(ResolvedType actualResolvedType, Class<?> type) {
        if (isNotVoid(type) || !isNotVoid(actualResolvedType.getErasedType())) {
            return typeResolver.resolve(type);
        }
        return actualResolvedType;
    }

    private boolean fromDataRespose(ResolvedType resolvedType) {
        return DataResponse.class.isAssignableFrom(resolvedType.getErasedType());
    }

    private static boolean isNotVoid(Class<?> response) {
        return Void.class != response
                       && void.class != response;
    }

}
